package services

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"server/app/common/request"
	"server/app/models"
	"server/global"
	"strconv"
	"strings"
	"time"

	"github.com/jassue/go-storage/storage"
	uuid "github.com/satori/go.uuid"
)

type mediaService struct{}

var MediaService = new(mediaService)

type outPut struct {
	Id   int64  `json:"id"`
	Path string `json:"path"`
	Url  string `json:"url"`
}

const mediaCacheKeyPre = "media:"

// 文件存储目录
func (mediaService *mediaService) makeFaceDir(business string) string {
	return global.App.Config.App.Env + "/" + business
}

// hashName生成文件名称(使用uuid)
func (mediaService *mediaService) HashName(fileName string) string {
	fileSuffix := path.Ext(fileName)
	return uuid.NewV4().String() + fileSuffix
}

// SaveImage 保存图片(公共读)
func (mediaService *mediaService) SaveImage(params request.ImageUpload) (result outPut, err error) {
	file, err := params.ImageFile.Open()
	if err != nil {
		err = errors.New("上传失败")
		return
	}
	defer file.Close()
	localPrefix := ""
	// 本地文件存放路径为 storage/app/public，由于在『（五）静态资源处理 & 优雅重启服务器』中，
	// 配置了静态资源处理路由 router.Static("/storage", "./storage/app/public")
	// 所以此处不需要将 public/ 存入到 mysql 中，防止后续拼接文件 Url 错误
	if global.App.Config.Storage.Default == storage.Local {
		localPrefix = "public" + "/"
	}
	key := mediaService.makeFaceDir(params.Business) + "/" + mediaService.HashName(params.ImageFile.Filename)
	disk := global.App.Disk()
	err = disk.Put(localPrefix+key, file, params.ImageFile.Size)
	if err != nil {
		return
	}
	image := models.Media{
		DiskType: string(global.App.Config.Storage.Default),
		SrcType:  1,             //图
		Src:      disk.Url(key), //key
	}
	err = global.App.DB.Create(&image).Error
	if err != nil {
		return
	}
	result = outPut{int64(image.ID.ID), key, disk.Url(key)}
	return
}

type videoOutput struct {
	MediaId int64  `json:"meidaId"`
	Video   int64  `json:"videoId"`
	Path    string `json:"path"`
	Url     string `json:"url"`
}

// SaveVideo 保存视频(公共读)
func (mediaService *mediaService) SaveVideo(params request.VideoUpload) (result videoOutput, err error) {
	file, err := params.VideoFile.Open()
	if err != nil {
		err = errors.New("上传失败")
		return
	}
	defer file.Close()
	localPrefix := ""
	if global.App.Config.Storage.Default == storage.Local {
		localPrefix = "public" + "/"
	}
	key := mediaService.makeFaceDir(params.Business) + "/" + mediaService.HashName(params.VideoFile.Filename)

	disk := global.App.Disk()
	err = disk.Put(localPrefix+key, file, params.VideoFile.Size)
	if err != nil {
		return
	}

	// 视频文件的本地路径
	videoLocalPath := localPrefix + key
	// 音频文件的键值和本地路径
	// audioKey := strings.TrimSuffix(key, filepath.Ext(key)) + ".mp3"
	audioBaseKey := mediaService.makeFaceDir("audio") + "/" + mediaService.HashName(params.VideoFile.Filename)
	audioKey := strings.TrimSuffix(audioBaseKey, filepath.Ext(audioBaseKey)) + ".mp3"
	audioLocalPath := localPrefix + audioKey

	/* // 确保 audio 文件夹存在
	err = os.MkdirAll(filepath.Dir(audioLocalPath), 0755)
	if err != nil {
		// 处理创建目录的错误
		return
	} */

	media := models.Media{
		DiskType: string(global.App.Config.Storage.Default),
		SrcType:  2, //视频
		Src:      key,
	}
	mediaErr := global.App.DB.Create(&media).Error
	if mediaErr != nil {
		return
	}

	video := models.Videos{
		UserID:      params.UserID,
		Title:       params.Title,
		Description: params.Description,
		CoverPath:   params.CoverPath,
		VideoPath:   disk.Url(key),
		Business:    params.Business,
	}
	videoErr := global.App.DB.Create(&video).Error
	if videoErr != nil {
		return
	}

	//异步提取文件
	go func(videoPath, audioPath string) {
		// 调用函数提取音频
		aAbs, audioFileName, err := ExtractAudioFromVideo(videoLocalPath, audioLocalPath)
		// ExtractAudioErr := ""
		if err != nil {
			// 如果提取音频失败
			// ExtractAudioErr = "提取音频失败"
			log.Printf("提取音频失败：%v", err)
			return
		}

		//转换成功之后写进数据库
		disk := global.App.Disk()
		audio := models.Audios{
			Url:     disk.Url(audioPath),
			VideoID: video.VideoID,
			Name:    audioFileName,
		}
		// log.Printf("Audio struct: %+v\n", audio)
		err = global.App.DB.Create(&audio).Error
		if err != nil {
			log.Printf("保存音频失败：%v", err)
			return
		}
		//检查文件大小
		fileInfo, err := os.Stat(aAbs)
		if err != nil {
			log.Printf("获取文件信息失败：%v", err)
			return
		}

		//文件大小小于等于5MB，开启语音识别
		size := fileInfo.Size()
		println("提出的audio大小", size)
		if fileInfo.Size() <= 5*1024*1024 {
			go func(audioLocalPath string) {
				fileName, err := mediaService.SpeechRecognition(audioLocalPath)
				if err != nil {
					log.Printf("语音识别失败：%v", err)
					return

				} else {
					log.Printf("字幕文件生成成功：%v", fileName)
				}

				//保存字幕文件
				subTitle := models.Subtitles{
					AudioID:      int(audio.ID.ID),
					VideoID:      video.VideoID,
					Name:         fileName,
					SubtitlePath: fileName,
				}
				err = global.App.DB.Create(&subTitle).Error
				if err != nil {
					return
				}
			}(aAbs)
		}

		/* //语音识别
		fileName, err := SpeechRecognition(audioLocalPath)
		fmt.Println("生成的字幕文件：", fileName)
		if err != nil {
			return
		} */

	}(videoLocalPath, audioLocalPath)

	result = videoOutput{int64(media.ID.ID), int64(video.VideoID), key, disk.Url(key)}
	return
}

/* 相对路径和绝对路径的问题 */
// ExtractAudioFromVideo 从视频文件中提取音频并保存为MP3格式
func ExtractAudioFromVideo(videoPath string, audioPath string) (string, string, error) {
	storage := global.App.Config.Storage.Disks.Local.RootDir + "/"
	vAbs, _ := filepath.Abs(storage + videoPath)
	aAbs, _ := filepath.Abs(storage + audioPath)

	// 确保 audio 文件夹存在
	audioDir := filepath.Dir(aAbs)
	if err := os.MkdirAll(audioDir, 0755); err != nil {
		// 处理创建目录的错误
		return "", "", fmt.Errorf("创建目录失败: %v", err)
	}

	// 构建 FFmpeg 命令来从视频提取音频:因为是ffmpeg命令生成的文件路径，audio文件夹没有时不会自动创建，导致报错(待解决)
	ffmpegCmd := exec.Command("ffmpeg", "-i", vAbs, "-codec:a", "libmp3lame", "-b:a", "96k", "-map", "a", aAbs, "-y") //压缩音频成96k

	// 获取命令的标准输出和标准错误
	var stderr bytes.Buffer
	ffmpegCmd.Stderr = &stderr

	// 运行 FFmpeg 命令
	if err := ffmpegCmd.Run(); err != nil {
		// 输出 FFmpeg 的错误信息
		return "", "", fmt.Errorf("ffmpeg error: %v - %s", err, stderr.String())
	}
	// 提取文件名
	audioFileName := filepath.Base(aAbs)
	return aAbs, audioFileName, nil
}

// GetUrlById 通过id获取文件url
func (mediaService *mediaService) GetUrlById(id int64) string {
	if id == 0 {
		return ""
	}
	var url string
	cacheKey := mediaCacheKeyPre + strconv.FormatInt(id, 10)
	exist := global.App.Redis.Exists(context.Background(), cacheKey).Val()
	if exist == 1 {
		url = global.App.Redis.Get(context.Background(), cacheKey).Val()
	} else {
		media := models.Media{}
		err := global.App.DB.First(&media, id).Error
		if err != nil {
			return ""
		}
		url = global.App.Disk(media.DiskType).Url(media.Src)
		global.App.Redis.Set(context.Background(), cacheKey, url, time.Second*3*24*3600)
	}
	return url
}
